package com.coffee.socketio;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.coffee.socketio.command.CommandEnum;
import com.coffee.socketio.command.CommandModel;
import com.coffee.socketio.command.CommandModelMapping;
import com.coffee.socketio.handler.HandlerMapping;
import com.coffee.socketio.handler.CoffeeHandler;
import com.coffee.socketio.handler.SocketioTextHandler;
import com.coffee.socketio.handler.SocketioJsonHandler;
import com.coffee.socketio.metadata.MessageType;
import com.coffee.socketio.metadata.ServerParameter;
import com.coffee.socketio.metadata.TextMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;

import java.util.*;

/**
 * @author: songkui
 * @since: 2023/3/6 15:35
 */
@Slf4j
public class SocketioInitialize {

    public static void initializeCommandModel(Map<Integer, CommandEnum> commandEnumMap) throws Exception {
        Map<Integer, CommandModel> modelMap = new HashMap<>();

        Collection<CommandEnum> collection = commandEnumMap.values();
        Iterator<CommandEnum> codeIterator = collection.iterator();
        int textCmd = 0;
        int jsonCmd = 0;
        while (codeIterator.hasNext()) {
            CommandEnum v = codeIterator.next();
            CommandModel commandModel = new CommandModel();
            commandModel.setNameSpace(v.namespace());
            commandModel.setEvent(v.event());
            commandModel.setCmd(v.cmd());
            commandModel.setVerifyType(v.verifyType());
            commandModel.setCmdName(v.cmdName());
            commandModel.setMessageType(v.messageType());

            MessageType messageType = v.messageType();
            switch (messageType) {
                case Text:
                    ++textCmd;
                    commandModel.setTextRequest((TextMessage) v.request().getConstructor().newInstance());
                    break;
                case Json:
                    ++jsonCmd;
                    break;

            }

            if (messageType.equals(MessageType.Text) && null == commandModel.getTextRequest()){
                throw new Exception("CommandModel "+ v.cmdName() +" MochaTextRequest is null");
            }

            modelMap.put(v.cmd(), commandModel);
            CommandModelMapping.addServerParam(new ServerParameter(commandModel.getNameSpace(), commandModel.getEvent(), commandModel.getMessageType(), v.request()));
        }

        CommandModelMapping.initializeCommandModel(modelMap);
        log.info("Command Model : textCmd size {};  jsonCmd size {}", textCmd, jsonCmd);

    }

    public static void initializeHandlerMapping(String handlerPackagePath, Map<Integer, CommandEnum> commandEnumMap) throws Exception {

        Map<String, SocketioTextHandler<?>> textSocketHandlerMap = new HashMap<>();
        Map<String, SocketioJsonHandler> textJsonSocketHandlerMap = new HashMap<>();


        ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
        provider.addIncludeFilter(new AnnotationTypeFilter(CoffeeHandler.class));
        Set<BeanDefinition> components = provider.findCandidateComponents(handlerPackagePath);
        Set<Integer> cmdSet = new HashSet<>();
        for (BeanDefinition component : components) {
            Class clazz = Class.forName(component.getBeanClassName());
            if (clazz.isAnnotationPresent(CoffeeHandler.class)) {
                CoffeeHandler coffeeHandler = (CoffeeHandler) clazz.getAnnotation(CoffeeHandler.class);
                if (null == coffeeHandler || StrUtil.isEmpty(coffeeHandler.value())) {
                    throw new Exception("Websocket handler error : '" + clazz.getName() + "' MochaEndpoint error ");
                }

                CommandEnum commandEnum = cmdNameToCmd(commandEnumMap, coffeeHandler.value());
                int cmd = commandEnum.cmd();
                MessageType messageType = commandEnum.messageType();
                switch (messageType) {
                    case Text: putSocketTextHandlerMapping(textSocketHandlerMap, commandEnum.event(), clazz); break;
                    case Json: putSocketTextJsonHandlerMapping(textJsonSocketHandlerMap, commandEnum.event(), clazz); break;
                }

                cmdSet.add(cmd);
            }
        }

//        if (cmdSet.size() != components.size()) {
//            throw new Exception("Mocha websocket handler repetition");
//        }

        HandlerMapping.initializeSocketTextHandler(textSocketHandlerMap);
        HandlerMapping.initializeSocketTextJsonHandler(textJsonSocketHandlerMap);

        log.info("Initialize socketTextHandlerMap size {} success", textSocketHandlerMap.size());
        log.info("Initialize socketTextJsonHandlerMap size {} success", textJsonSocketHandlerMap.size());

    }

    private static CommandEnum cmdNameToCmd(Map<Integer, CommandEnum> commandEnumMap, String cmdName) throws Exception {
        if (MapUtil.isEmpty(commandEnumMap)) {
            throw new Exception("CommandEnum is null, please implement interface CommandEnum");
        }
        Collection<CommandEnum> collection = commandEnumMap.values();
        Iterator<CommandEnum> codeIterator = collection.iterator();
        while (codeIterator.hasNext()) {
            CommandEnum v = codeIterator.next();
            if (cmdName.contains(".")) {
                cmdName = StrUtil.subAfter(cmdName, '.', true);
            }
            if (v.cmdName().equals(cmdName)) {
                return v;
            }
        }
        throw new Exception("'" + cmdName + "' CommandEnum in not this cmdName");
    }


    private static void putSocketTextHandlerMapping(Map<String, SocketioTextHandler<?>> handlerMap, String event, Class<SocketioTextHandler> clazz) throws Exception {
        if (null == clazz || StrUtil.isEmpty(event)) {
            throw new Exception("build socket text Handler exception");
        }
        handlerMap.put(event, Optional.ofNullable(SpringBeanContext.getBean(clazz)).orElseThrow(() -> new Exception("build socket text handler Mapping no exception")));
    }

    private static void putSocketTextJsonHandlerMapping(Map<String, SocketioJsonHandler> handlerMap, String event, Class<SocketioJsonHandler> clazz) throws Exception {
        if (null == clazz || StrUtil.isEmpty(event)) {
            throw new Exception("build socket text json Handler exception");
        }
        handlerMap.put(event, Optional.ofNullable(SpringBeanContext.getBean(clazz)).orElseThrow(() -> new Exception("build socket text handler json Mapping no exception")));
    }


}
